#ifndef TABLE_MANAGER_H
#define TABLE_MANAGER_H

#include "bufmanager/BufPageManager.h"
#include "fileio/FileManager.h"
#include "utils/pagedef.h"
#include "page.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <iostream>
#include <string>
#include <vector>
#include <set>

using namespace std;

class TableManager {
    string dbName;

    FileManager* fm;
    BufPageManager* bpm;
    int fileId;

    Page* managePage;
    Table table;
    int recordLen;
    vector<int> recordCnt;
    set<string> primaryMap;

    bool searchRecord(string key, string value, int& pageId, int& recordId) {
        //search the record which record[key] == value

        for (int i = 0; i < recordCnt.size(); i++) {
            Page dataPage(bpm, fileId, 1 + i);
            for (int j = 0; j < recordCnt[i]; j++) {
                Record record(table);
                record.setData(dataPage.getRecord(j, recordLen));
                if (record.find(key) == value) {
                    pageId = 1 + i;
                    recordId = j;
                    return true;
                }
            }
        }

        //cout << "Record not found in TableManager::searchRecord" << endl;
        return false;
    }

    void initPrimaryMap() {
        primaryMap.clear();
        for (int i = 0; i < recordCnt.size(); i++) {
            Page dataPage(bpm, fileId, 1 + i);
            for (int j = 0; j < recordCnt[i]; j++) {
                Record record(table);
                record.setData(dataPage.getRecord(j, recordLen));
                primaryMap.insert(record.getPrimaryValue());
            }
        }
    }

public:
    TableManager(string dbName) {
        this->dbName = dbName;

        fm = new FileManager();
        bpm = new BufPageManager(fm);
    }

    Table getTable() {
        return table;
    }

    bool createTable(Table table) {
        //MANAGE PAGE FORM:
        //tableLen, table[tableLen]
        //recordLen
        //dataPageNum, recordCnt[dataPageNum]

        string path = dbName + "/" + table.getName();
        if (fm->openFile(path.c_str(), fileId) == false) {
            fm->createFile(path.c_str());
            fm->openFile(path.c_str(), fileId);

            managePage = new Page(bpm, fileId, 0);

            vector<unsigned int> tableData = table.getData();
            int cnt = 0;
            managePage->write(cnt++, tableData.size());
            for (int i = 0; i < tableData.size(); i++) {
                managePage->write(cnt++, tableData[i]);
            }
            managePage->write(cnt++, table.getRecordLen());
            managePage->write(cnt++, 0);

            managePage->writeBack();

            if (fm->closeFile(fileId) == 0) {
                fileId = -1;
                delete managePage;
                managePage = NULL;
            }

            return true;
        }

        cout << "The table already exist in TableManager::createTable" << endl;
        return false;
    }

    bool openTable(string tableName) {
        //MANAGE PAGE FORM:
        //tableLen, table[tableLen]
        //recordLen
        //dataPageNum, recordCnt[dataPageNum]

        string path = dbName + "/" + tableName;
        if (fm->openFile(path.c_str(), fileId) == true) {
            managePage = new Page(bpm, fileId, 0);

            int cnt = 0;
            int tableLen = managePage->read(cnt++);
            vector<unsigned int> tableData;
            for (int i = 0; i < tableLen; i++) {
                tableData.push_back(managePage->read(cnt++));
            }
            table.setData(tableData);
            recordLen = managePage->read(cnt++);
            int dataPageNum = managePage->read(cnt++);
            recordCnt.clear();

            int pageId = 0;
            Page* page = managePage;
            for (int i = 0; i < dataPageNum; i++) {
                if (cnt == page->MAX_ADDRESS) {
                    if (pageId != 0) {
                        delete page;
                    }
                    if (pageId == 0) {
                        pageId = dataPageNum + 1;
                    } else {
                        pageId++;
                    }
                    page = new Page(bpm, fileId, pageId);
                    cnt = 0;
                }
                recordCnt.push_back(page->read(cnt++));
            }
            if (pageId != 0) {
                delete page;
            }

            initPrimaryMap();
            return true;
        }

        cout << "The table does not exist in TableManager::openTable" << endl;
        return false;
    }

    bool closeTable() {
        int cnt = managePage->read(0) + 2; //address for dataPageNum
        managePage->write(cnt++, recordCnt.size());

        int pageId = 0;
        Page* page = managePage;
        for (int i = 0; i < recordCnt.size(); i++) {
            if (cnt == page->MAX_ADDRESS) {
                if (pageId == 0) {
                    pageId = recordCnt.size() + 1;
                } else {
                    pageId++;
                }
                page->writeBack();
                delete page;
                page = new Page(bpm, fileId, pageId);
                cnt = 0;
            }
            page->write(cnt++, recordCnt[i]);
        }
        if (cnt > page->MAX_ADDRESS) {
            cout << "Memory not enough in TableManager::closeTable" << endl;
        }

        page->writeBack();
        for (int i = 1; i <= recordCnt.size(); i++) {
            Page dataPage(bpm, fileId, i);
            dataPage.writeBack();
        }

        if (fm->closeFile(fileId) == 0) {
            fileId = -1;
            delete page;
            page = NULL;
            return true;
        }

        cout << "Close file error in TableManager::closeTable" << endl;
        return false;
    }

    void dropTable(string tableName) {
        string path = dbName + "/" + tableName;
        remove(path.c_str());
    }

    bool insertRecord(Record record) {
        //insert a record in this database
        if (record.notNullError()) {
            cout << "Not null error when insert record : " << record.getPrimaryValue() << endl;
            return false;
        }

        if (primaryMap.find(record.getPrimaryValue()) != primaryMap.end()) {
            cout << "Insert existed record in TableManager::insertRecord for " << record.getPrimaryValue() << endl;
            return false;
        }

        vector<unsigned int> data = record.getData();

        if (data.size() == recordLen) {
            //int pageId = 0, recordId = 0; //check if the record is repeated
            //if (searchRecord(table.getPrimaryKey(), record.getPrimaryValue(), pageId, recordId)) {
            //    cout << "Repeated Record in TableManager::insertRecord" << endl;
            //    return false;
            //}

            for (int i = 0; i < recordCnt.size(); i++) {
                if ((recordCnt[i] + 1) * recordLen <= managePage->MAX_ADDRESS) {
                    Page dataPage(bpm, fileId, 1 + i);
                    dataPage.setRecord(recordCnt[i], data);
                    primaryMap.insert(record.getPrimaryValue());
                    recordCnt[i]++;
                    return true;
                }
            }
            Page dataPage(bpm, fileId, 1 + recordCnt.size());
            dataPage.setRecord(0, data);
            primaryMap.insert(record.getPrimaryValue());
            recordCnt.push_back(1);
            return true;
        }

        cout << "Insert record with wrong length in TableManager::insertRecord" << endl;
        return false;
    }

    bool deleteRecord(string primaryValue) {
        //delete the record which record[key] == value

        int pageId = 0, recordId = 0;
        if (searchRecord(table.getPrimaryKey(), primaryValue, pageId, recordId)) {
            Page dataPage(bpm, fileId, pageId);
            for (int i = recordId; i + 1 < recordCnt[pageId - 1]; i++) {
                for (int j = 0; j < recordLen; j++) {
                    dataPage.write(i * recordLen + j, dataPage.read((i + 1) * recordLen + j));
                }
            }
            recordCnt[pageId - 1]--;
            primaryMap.erase(primaryValue);

            return true;
        }

        cout << "Record not found in TableManager::deleteRecord" << endl;
        return false;
    }

    bool updateRecord(Record record) {
        //update the record which record[key] == value to this record
        if (record.notNullError()) {
            cout << "Not null error when update record : " << record.getPrimaryValue() << endl;
            return false;
        }

        int pageId = 0, recordId = 0;
        if (searchRecord(table.getPrimaryKey(), record.getPrimaryValue(), pageId, recordId)) {
            Page dataPage(bpm, fileId, pageId);
            dataPage.setRecord(recordId, record.getData());
            return true;
        }

        cout << "Record not found in TableManager::updateRecord" << endl;
        return false;
    }

    bool queryRecord(string key, string value, Record& record) {
        Record ret(table);

        int pageId = 0, recordId = 0;
        if (searchRecord(key, value, pageId, recordId)) {
            Page dataPage(bpm, fileId, pageId);
            ret.setData(dataPage.getRecord(recordId, recordLen));
            record = ret;
            return true;
        }

        cout << "Record not found in TableManager::queryRecord" << endl;
        return false;
    }

    bool queryRecord(string primaryValue, Record& record) {
        return queryRecord(table.getPrimaryKey(), primaryValue, record);
    }

    vector<Record> getRecords() {
        vector<Record> records;

        for (int i = 0; i < recordCnt.size(); i++) {
            Page dataPage(bpm, fileId, 1 + i);
            for (int j = 0; j < recordCnt[i]; j++) {
                Record record(table);
                record.setData(dataPage.getRecord(j, recordLen));
                records.push_back(record);
            }
        }

        return records;
    }

    int getRecordNumber() {
        int ret = 0;

        for (int i = 0; i < recordCnt.size(); i++) {
            ret += recordCnt[i];
        }

        return ret;
    }
};

#endif
